package controller;

import java.awt.Cursor;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.Collections;
import java.util.Set;

import javax.swing.JComponent;

import view.*;
import model.*;

public class GameController {

	private GameModel model;
	private GameComponent view;
	
	private Point tableSelectionBoxOrigin;
	private TableSelection tableSelection;
	private boolean tableDragging;
	private boolean handDragging;
	
	public GameController(GameModel model, GameComponent tview) {
		this.model = model;
		this.view = tview;
		
		tableSelectionBoxOrigin = null;
		tableSelection = null;
		tableDragging = false;
		handDragging = false;
		addMouseListeners();
	}
	
	public void addMouseListeners() {
		
		
		view.getTable().addMouseListener(new MouseListener() {

			@Override
			public void mouseClicked(MouseEvent e) {
				if(e.getButton()!=MouseEvent.BUTTON1)
					return;
				mouseClickedTable(e.getX(),e.getY());
				
			}

			@Override
			public void mouseEntered(MouseEvent e) {
				
			}

			@Override
			public void mouseExited(MouseEvent e) {
				
			}

			@Override
			public void mousePressed(MouseEvent e) {
				if(e.getButton()!=MouseEvent.BUTTON1)
					return;
				mousePressedTable(e.getX(),e.getY());
				
			}

			@Override
			public void mouseReleased(MouseEvent e) {
				if(e.getButton()!=MouseEvent.BUTTON1)
					return;
				mouseReleasedTable(e.getX(),e.getY());
				
			}
		});
		view.getTable().addMouseMotionListener(new MouseMotionListener() {
			@Override
			public void mouseDragged(MouseEvent e) {
				mouseDraggedTable(e.getX(), e.getY());
			}

			@Override
			public void mouseMoved(MouseEvent e) {
				mouseMovedTable(e.getX(), e.getY());

			}
		});
		
		for(Position pos: new Position[] {Position.NORTH, Position.SOUTH}) {
			final Position tpos = pos;
			view.getHand(tpos).addMouseListener(new MouseListener() {
	
				@Override
				public void mouseClicked(MouseEvent e) {
					mouseClickedHand(tpos,e.getX(),e.getY());
				}
	
				@Override
				public void mouseEntered(MouseEvent e) {
					
				}
	
				@Override
				public void mouseExited(MouseEvent e) {
					
				}
	
				@Override
				public void mousePressed(MouseEvent e) {
					mousePressedHand(tpos,e.getX(),e.getY());
				}
	
				@Override
				public void mouseReleased(MouseEvent e) {
					mouseReleasedHand(tpos,e.getX(),e.getY());
				}
			});
			view.getHand(tpos).addMouseMotionListener(new MouseMotionListener() {
				@Override
				public void mouseDragged(MouseEvent e) {
					mouseDraggedHand(tpos, e.getX(), e.getY());
				}

				@Override
				public void mouseMoved(MouseEvent e) {
					mouseMoveHand(tpos, e.getX(), e.getY());

				}
			});
		}
		
		
		
		view.addMouseMotionListener(new MouseMotionListener() {
			@Override
			public void mouseDragged(MouseEvent e) {
			}

			@Override
			public void mouseMoved(MouseEvent e) {
			}
		});
		
		view.getTable().addMouseListener(new RightClickListener(view.getTable()));
	}
	
	public Point getSelectionBoxOrigin() {
		return tableSelectionBoxOrigin;
	}
	public TableSelection getTableSelection() {
		return tableSelection;
	}
	
	/** Selects cards on the table given a selection box from (ax,ay) to (by,by).
	 */
	public void selectCardsInBox(int ax, int ay, int bx, int by) {
		//Reorganize so that (ax, ay) is the top left of the box
		int t = ax+bx;
		ax = Math.min(ax, bx);
		bx = t-ax;
		t = ay+by;
		ay = Math.min(ay, by);
		by = t-ay;
		
		
		TableSelection ret = new TableSelection();
		for(CardStack stack: model.getTableCards()) {
			if(ax<stack.getX() && stack.getX()<bx && ay<stack.getY() && stack.getY()<by) {
				ret.add(new StackFragment(stack, stack.size()));
			}
		}
		
		if(ret.size()>0)
			setTableSelection(ret);
		else
			setTableSelection((TableSelection)null);
	}
	public void shuffleTableSelection() {
		if(tableSelection==null) return;
		for(StackFragment frag : tableSelection)
			Collections.shuffle(frag.stack.getCards());
		view.getTable().repaint();
	}
	/** Flips the table selection.
	 * If the stacks in the selection are all face down, then turn them all face up.
	 * Otherwise, turn them all face down.
	 */
	public void flipTableSelection() {
		if(tableSelection==null) return;
		boolean allFaceDown = true;
		for(StackFragment frag : tableSelection)
			if(frag.stack.getFaceUp())
				allFaceDown = false;
		for(StackFragment frag : tableSelection)
			frag.stack.setFaceUp(allFaceDown);
	}
	
	public void spreadTableSelection() {
		if(tableSelection==null) return;
		TableSelection endSelection = new TableSelection();
		for(StackFragment frag : tableSelection) {
			CardStack stack = frag.stack;
			endSelection.add(new StackFragment(stack, 1));
			int max = stack.size();
			for(int n=0; n<max-1; n++) {
				stack = model.getTable().moveSubStack(stack, max-n-1, stack.getX()+15, stack.getY()+1);
				endSelection.add(new StackFragment(stack, 1));
			}
		}
		this.setTableSelection(endSelection);
	}
	public void setTableSelection(StackFragment frag) {
		tableSelection = new TableSelection(frag);
		view.getTable().setSelection(tableSelection);
	}
	public void setTableSelection(TableSelection ts) {
		tableSelection = ts;
		view.getTable().setSelection(ts);
	}
	
	/** Enables a selection box by giving the origin corner of the box. */
	public void setTableSelectionBoxOrigin(Point p) {
		tableSelectionBoxOrigin = p;
		view.getTable().setSelectionBoxOrigin(p);
	}
	public void disableTableSelectionBox() {
		tableSelectionBoxOrigin = null;
		view.getTable().setSelectionBoxOrigin(null);
	}
	
	
	public void enableTableDragging() {
		tableDragging = true;
		view.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));	
		view.getTable().setDragging(true);
	}
	public void disableTableDragging() {
		tableDragging = false;
		view.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));	
		view.getTable().setDragging(false);
	}
	
	//This method really shouldn't be here TODO its just for a hack for now
	public boolean isHandDragging() {
		return handDragging;
	}
	public void enableHandDragging() {
		handDragging = true;
		view.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));	
		view.getHand(MetaDeckController.getInstance().getMyPosition()).repaint();
	}
	public void disableHandDragging() {
		handDragging = false;
		view.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));	
		view.getHand(MetaDeckController.getInstance().getMyPosition()).repaint();
	}
	
	/**
	 * Utility function returning true if the component contains the screen coordinate point (x,y).
	 * The component API apparently doesn't have this function...
	 */
	public static boolean containsPoint(JComponent c, int x, int y) {
		Point p = c.getLocationOnScreen();
		return x>=p.x && x<p.x+c.getWidth() && y>=p.y && y<p.y+c.getHeight();
	}
	
	
	
	
	
	
	//-----------------Mouse listener methods------------------------
	
	public void mousePressedTable(int x, int y) {
		StackFragment si = view.getTable().getSelectionAt(x, y);
		if(si==null) {
			tableSelectionBoxOrigin = new Point(x, y);
			setTableSelectionBoxOrigin(tableSelectionBoxOrigin);
		} else {
			if(tableSelection==null || !tableSelection.containsFragment(si.stack)  
					|| (1<si.top && si.top<si.stack.size()) ) {
				setTableSelection(si);
			}
			enableTableDragging();
		}
		view.repaint();
	}
	
	public void mouseReleasedTable(int x, int y) {
		int xScreen = x+view.getTable().getLocationOnScreen().x;
		int yScreen = y+view.getTable().getLocationOnScreen().y;
		if(tableSelectionBoxOrigin!=null) {
			selectCardsInBox(tableSelectionBoxOrigin.x, tableSelectionBoxOrigin.y, x, y);
			disableTableSelectionBox();
			view.repaint();
		}
		if(tableDragging) {
			Position playerPosition = MetaDeckController.getInstance().getMyPosition();
			if(containsPoint(view.getTable(), xScreen, yScreen)) {
				TableSelection dragged = tableSelection;
				
				StackFragment si = view.getTable().getSelectionAt(x, y, dragged);
				CardStack toStack = null;
				if(si!=null && !dragged.containsFragment(si.stack)) {
					toStack = si.stack;
					for(StackFragment frag : dragged) 
						model.getTable().moveSubStack(frag.stack, frag.top, toStack);
				} else {
					for(StackFragment frag : dragged) {
						if(toStack==null)
							toStack = model.getTable().moveSubStack(frag.stack, frag.top, x, y);
						else
							model.getTable().moveSubStack(frag.stack, frag.top, toStack);
					}
				}
				setTableSelection(new StackFragment(toStack, dragged.totalSize()));
			} else if(containsPoint(view.getHand(playerPosition), xScreen, yScreen)) {
				TableSelection dragged = tableSelection;
				for(StackFragment frag : dragged) 
					model.moveToHand(frag.stack, frag.top, playerPosition);
				setTableSelection((TableSelection)null);
				view.getHand(playerPosition).repaint();
			}
			disableTableDragging();
			view.repaint();
		}
	}
	
	public void mouseClickedTable(int x, int y) {
		
	}
	
	public void mouseDraggedTable(int x, int y) {
		if(tableDragging || handDragging) {
			view.repaint();
		} else {
			view.getTable().repaint();
		}
	}
	
	public void mouseMovedTable(int x, int y)
	{
		if (view.getTable().getCardAt(x, y) != null)
			view.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));	 
		else
			view.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
		view.getTable().repaint();
		
	}
	
	public void mousePressedHand(Position pos, int x, int y) {
		if(pos!=MetaDeckController.getInstance().getMyPosition())
			return;
		Card card = view.getHand(pos).getCardAt(x, y);
		Set<Card> handSelection = view.getHand(pos).getSelection();
		if(handSelection.contains(card)) {
			enableHandDragging();
		}
	}
	
	public void mouseReleasedHand(Position pos, int x, int y) {
		if(pos!=MetaDeckController.getInstance().getMyPosition())
			return;
		int xScreen = x+view.getHand(pos).getLocationOnScreen().x;
		int yScreen = y+view.getHand(pos).getLocationOnScreen().y;
		if(handDragging) {
			if(containsPoint(view.getTable(), xScreen, yScreen)) {
				x = xScreen-view.getTable().getLocationOnScreen().x;
				y = yScreen-view.getTable().getLocationOnScreen().y;
				StackFragment si = view.getTable().getSelectionAt(x, y);
				CardStack toStack;
				if(si!=null) {
					toStack = si.stack;
				} else {
					toStack = new CardStack(x, y);
					model.getTableCards().add(toStack);
					
				}
				Set<Card> handSelection = view.getHand(pos).getSelection();
				model.moveFromHand(handSelection, pos, toStack);
				setTableSelection(new StackFragment(toStack, handSelection.size()));
				view.getHand(pos).clearSelection();
			}
			disableHandDragging();
			view.repaint();
		}
	}
	
	public void mouseClickedHand(Position pos, int x, int y) {
		if(pos!=MetaDeckController.getInstance().getMyPosition())
			return;
		view.getHand(pos).toggleCardAt(x, y);
	}
	
	public void mouseDraggedHand(Position pos, int x, int y) 
	{
		view.repaint();
	}
	
	public void mouseMoveHand(Position pos, int x, int y)
	{
		
		Position myPos = MetaDeckController.getInstance().getMyPosition();
		if (view.getHand(myPos).getCardAt(x, y) != null)
			
		{
			view.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));	
		}
		else
		{
			view.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
		}
	}
}
